package ex3.render.raytrace;

import java.awt.Color;
import java.awt.image.BufferedImage;
import ex3.parser.Element;
import ex3.parser.SceneDescriptor;
import ex3.render.IRenderer;
import java.io.File;
import java.io.IOException;
import java.util.Iterator;
import javax.imageio.ImageIO;
import math.Ray;
import math.Vec;

public class RayTracer implements IRenderer {
	
	protected int width;
    protected int height;
    protected Scene scene;
    protected File path;
    protected java.awt.image.BufferedImage backgroundImage;


	/**
	 * Inits the renderer with scene description and sets the target canvas to
	 * size (width X height). After init renderLine may be called
	 * 
	 * @param sceneDesc
	 *            Description data structure of the scene
	 * @param width
	 *            Width of the canvas
	 * @param height
	 *            Height of the canvas
	 * @param path
	 *            File path to the location of the scene. Should be used as a
	 *            basis to load external resources (e.g. background image)
	 */
	@Override
	public void init(SceneDescriptor sceneDesc, int width, int height, File path) {
		this.width = width;
        this.height = height;
        this.path = path;
        scene = new Scene();
        scene.init(sceneDesc.getSceneAttributes());
        if(scene.backgroundTex != null)
            initBackground();
        Element e;
        for(@SuppressWarnings("rawtypes")
		Iterator iterator = sceneDesc.getObjects().iterator(); iterator.hasNext(); scene.addObjectByName(e.getName(), e.getAttributes()))
            e = (Element)iterator.next();

        scene.setCameraAttributes(sceneDesc.getCameraAttributes());

	}

	private void initBackground() {
		backgroundImage = new java.awt.image.BufferedImage(width, height, 1);
        File file = new File((new StringBuilder(String.valueOf(path.getParent()))).append(File.separator).append(scene.backgroundTex).toString());
        try
        {
            backgroundImage = ImageIO.read(file);
        }
        catch(IOException e)
        {
            e.printStackTrace();
        }		
	}

	/**
	 * Renders the given line to the given canvas. Canvas is of the exact size
	 * given to init. This method must be called only after init.
	 * 
	 * @param canvas
	 *            BufferedImage containing the partial image
	 * @param line
	 *            The line of the image that should be rendered.
	 */
	@Override
	public void renderLine(BufferedImage canvas, int line) {
		int y = line;
	    double ratioHeight = 1.0D;
	    double ratioWidth = 1.0D;
	    if(backgroundImage != null)
	    {
	        ratioHeight = (double)backgroundImage.getHeight() / (double)height;
	        ratioWidth = (double)backgroundImage.getWidth() / (double)width;
	    }
	    for(int x = 0; x < width; x++)
	    {
	        java.awt.Color castRayColor = rayCasting(x, y);
	        if(castRayColor != null)
	            canvas.setRGB(x, y, castRayColor.getRGB());
	        else
	            canvas.setRGB(x, y, backgroundImage.getRGB((int)((double)x * ratioWidth), (int)((double)y * ratioHeight)));
	    }

	}

	private Color rayCasting(int x, int y) {
		Ray ray = scene.camera.buildRay(x, y, height, width);
        RayImpact hit = scene.findIntersection(ray, false);
        if((backgroundImage != null) & (hit == null))
            return null;
        if(scene.superSampWidth > 1)
        {
            Vec finalColor = new Vec();
            for(int i = 0; i < scene.superSampWidth; i++)
            {
                for(int j = 0; j < scene.superSampWidth; j++)
                {
                    Ray newRay = scene.camera.buildRay((double)x + (double)i / (double)scene.superSampWidth, (double)y + (double)j / (double)scene.superSampWidth, height, width);
                    RayImpact newHit = scene.findIntersection(newRay, false);
                    Vec color = scene.calcColor(newHit, newRay, 0);
                    finalColor.add(color);
                }

            }

            finalColor.scale(1.0D / Math.pow(scene.superSampWidth, 2D));
            return finalColor.toColor();
        } else
        {
            return scene.calcColor(hit, ray, 0).toColor();
        }

	}

}
